#!/usr/bin/env python3
''' Simplify the Yuketang video's playing progress.
:min-ver 3.7
:doc Original Documentation at https://frontop137.github.io/2020/10/31/%E7%88%AC%E8%99%AB/%E9%9B%A8%E8%AF%BE%E5%A0%82%E7%BD%91%E8%AF%BE%E8%87%AA%E5%8A%A8%E6%92%AD%E6%94%BE/
:doc Forked from https://github.com/frontop137/yuketang.cn-video-automatically-playing
:author frontop137
:date 2020-10-31
:contributor Tiger3018
:date 2022-10-27
'''
import requests
import json
import time

prefix = "https://courses.cqu.edu.cn"
classroomid = '12738632'
sign = '8qfmQeztftv'
term = 'latest'
universityid = '2930'
cookieContent = 'sessionid=!TODO!'


headers1 = {'user-agent': 'Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.'
                         '0.4240.111 Safari/537.36',
           'cookie': cookieContent,
           'xtbz': 'cloud'
           }

headers2 = {'user-agent': 'Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 '
                            '(KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36',
            'cookie': cookieContent,
        #    'referer': video_url[video_id.index(id)],
            'xtbz': 'cloud',
            'content-type': 'application/json'
            }

def get_videoid(crid):
    """
    获取需要观看视频的编号
    :param crid: 教室id
    :return: 储存着需要观看视频编号的列表
    """
    chap_url = prefix + '/mooc-api/v1/lms/learn/course/' \
               'chapter?cid=%s&sign=%s&term=%s&uv_id=%s' % (crid, sign, term, universityid)
    response = requests.get(chap_url, headers=headers1)
    response.encoding = response.apparent_encoding
    jsdata = json.loads(response.text)
    cid = jsdata['data']['course_id']
    course_chapters = jsdata['data']['course_chapter']
    vid = []
    for chap in course_chapters:
        for sec in chap['section_leaf_list']:
            if sec['name'] != '章节测试题' and 'leaf_list' in sec:#sec['name'].find('作业') == -1:
                # print(sec)
                vid += map(lambda x: x['id'], sec['leaf_list'])
    # delete complete video
    schedule_url = prefix + '/mooc-api/v1/lms/learn/course/schedule?cid=%s&sign=%s&term=%s&uv_id=%s' % (crid, sign, term, universityid)
    response = requests.get(schedule_url, headers=headers1)
    response.encoding = response.apparent_encoding
    jsdata = json.loads(response.text)
    leaf = jsdata['data']['leaf_schedules']
    # print(jsdata)
    for id in vid.copy():
        # print(id, str(id) in leaf)
        if str(id) in leaf and leaf[str(id)] == 1:
            vid.remove(id)
    return list(map(lambda x: str(x), vid)), cid


def get_videourl(vid):
    """
    获取视频url函数
    :param vid: 视频id列表
    :return: 视频url列表
    """
    url_head = prefix + '/pro/lms/%s/%s/video/' % (sign, classroomid)
    vurl = list(map(lambda x: url_head+x, vid))
    return vurl


def get_userinfo():
    user_url = prefix + '/edu_admin/get_user_basic_info/?term=latest&uv_id=%s' % universityid
    response = requests.get(user_url, headers=headers1)
    try: 
        print('用户姓名：', json.loads(response.text)['data']['user_info']['name'])
    except Exception:
        print(response.content)
        exit(1)
    userid = json.loads(response.text)['data']['user_info']['user_id']
    return userid


userid = get_userinfo()
video_id, courseid = get_videoid(classroomid)
# print(courseid, video_id)
video_url = get_videourl(video_id)

# 获取视频时长
def get_videolen(vid, headers2):
    '''deprecated
    '''
    ask_video_url = prefix + '/api/open/audiovideo/' \
                    'playurl?_date=%s&term=latest&video_id=%s&' \
                    'provider=cc&file_type=1&' \
                    'is_single=0' % (str(int(time.time()*1000)), get_videoinfo(vid)[1])
    origin_url = requests.get(ask_video_url, headers=headers2)
    origin_url.encoding = origin_url.apparent_encoding
    try:
        origin_url = json.loads(origin_url.text)['data']['playurl']['sources']['quality10'][0]
    except:
        origin_url = json.loads(origin_url.text)['data']['playurl']['sources']['quality20'][0]
    # 计算时长
    cap = cv2.VideoCapture(origin_url)
    if cap.isOpened():
        fps = cap.get(5)
        framenum = cap.get(7)
        duration = int(framenum / fps)
        return duration
    else:
        return 0

# 获取视频信息
def get_videoinfo(vid):
    """
    获取视频信息函数
    :param vid: 视频id
    :return: 第一个参数为sku_id（type->int）,第二个参数为cc(type->str)
    """
    info_url = prefix + '/mooc-api/v1/lms/learn/leaf_info/' \
                '%s/%s/?sign=%s&term=latest&uv_id=%s' % (classroomid, vid, sign, universityid)
    while True:
        try:
            info = requests.get(info_url, headers=headers2)
            info.encoding = info.apparent_encoding
            info = json.loads(info.text)['data']
            return info['sku_id'], info['content_info']['media']['ccid']
        except:
            print('Failed to get video info, retrying...', end='\r')

def get_rate(id, headers2):# -> tuple[float,bool]:
    rate = requests.get(prefix + 
                        '/video-log/get_video_watch_progress'
                        '/?cid=%s&user_id=%s&classroom_id=%s&video_typ'
                        'e=video&vtype=rate&video_id=%s&snapshot=1&term=latest&uv_id=%s'
                        % (str(courseid), str(userid), classroomid, id, universityid),
                        headers=headers2)
    rate.encoding = rate.apparent_encoding
    try:
        current_rate = json.loads(rate.text)[id]
    except:
        return 0.0, False # null, since no play history
    return float(current_rate['rate']), bool(current_rate['completed'])

def print_rate(id, width):# -> tuple[bool, length]:
    try:
        rate = requests.get(prefix + 
                        '/video-log/detail'
                        '/?classroom_id=%s&user_id=%s&video_id=%s&term=latest&uv_id=%s'
                        % (classroomid, userid, id, universityid),
                        headers=headers2)
        rate.encoding = rate.apparent_encoding
        current_heartbeat = json.loads(rate.text)["data"]["heartbeat"]
    except:
        return False, -1
    if not current_heartbeat:
        bar = ''.join(['-' for i in range(width)])
        print(f'\r| {bar} | failed to get heartbeat', end='\r')
        return False, -1
    try:
        length = current_heartbeat["video_length"]
        bar = ''.join(['█' if i / width * length < current_heartbeat["result"][0]["e"] else '-' for i in range(width)])
        percent = current_heartbeat["rate"]
        print(f'\r| {bar} | {percent:.3f} t={length}', end='\r')
    except:
        length = -1
        bar = ''.join(['-' for i in range(width)])
        percent = current_heartbeat["rate"]
        print(f'\r| {bar} | {percent}', end='\r')
    return bool(current_heartbeat["completed"]), length

for id in video_id:
    print('正在观看视频id：', id)
    if get_rate(id, headers2)[1]:
        continue

    # 心跳包post请求网址
    heartbeat_url = prefix + '/video-log/heartbeat/'

    # length = get_videolen(id, headers2)
    
    length = -1
    sku_id, cc = get_videoinfo(id)

    hb_list = []
    # 心跳包模板
    template = {
        'i': 5,
        'et': '',
        'p': 'web',
        'n': 'cc',
        'lob': 'cloud4',
        'cp': 0,  # 观看进度
        'fp': 0,
        'tp': 0,
        'sp': 1,
        'ts': int(time.time() * 1000),
        'u': userid,  # 用户id
        'uip': '',
        'c': courseid,   # 课程id
        'v': id,       # 视频id
        'skuid': sku_id,   # skuid
        'classroomid': classroomid,   # 教室id
        'cc': cc,
        'd': length,          # 视频时长
        'pg': str(id) + '_12bvp',
        'sq': 0,   # 心跳包序列
        't': 'video'
    }

    # 先发送一次空包
    requests.post(heartbeat_url, headers=headers2, data=json.dumps({"heart_data": []}))

    # loadstart
    ls = template.copy()
    ls['et'] = 'loadstart'
    ls['d'] = length
    ls['sq'] = 1
    hb_list.append(ls)

    # seeking
    sk = template.copy()
    sk['et'] = 'seeking'
    sk['sq'] = 2
    hb_list.append(sk)

    # loadeddata
    ld = template.copy()
    ld['et'] = 'loadeddata'
    ld['sq'] = 3
    hb_list.append(ld)

    # play
    play = template.copy()
    play['et'] = 'play'
    play['sq'] = 4
    hb_list.append(play)

    # playing
    playing = template.copy()
    playing['et'] = 'playing'
    playing['sq'] = 5
    hb_list.append(playing)

    sq = 6
    pos_at = 0
    def hb(length, sq, cp):
        hb = template.copy()
        hb['et'] = 'heartbeat'
        hb.update({'et': 'heartbeat', 'd': length, 'sq': sq, 'cp': cp})
        return hb
    
    while True:
        append_num = [10, 10 - len(hb_list)]
        hb_list += map(lambda i: hb(length, sq + i, pos_at + 2 * i), range(0, append_num[1]))
        sq += append_num[1]
        pos_at += 2 * append_num[1] # will get vacancy in the video progress
        if length > 0 and pos_at > length: # roll back to the start of the video
            pos_at -= length
        if len(hb_list) == append_num[0]:
            hb_data = {'heart_data': hb_list}
            while True:
                try:
                    send = requests.post(heartbeat_url, headers=headers2, data=json.dumps(hb_data))
                    if send.status_code == 200:
                        break
                except:
                    pass
                print('\r- Retrying...', end='\r')
                time.sleep(.3)
            # progress, rate = get_rate(id, headers2)
            complete, length = print_rate(id, 50)
            if complete:
                break
            # print(hb_list)
            hb_list.clear()
            time.sleep(.1)

    # 再发最后一个heartbeat
    hb = template.copy()
    sq += 1
    hb['et'] = 'heartbeat'
    hb['cp'] = length
    hb['sq'] = sq
    hb_list.append(hb)

    # pause
    pause = template.copy()
    sq += 1
    pause['et'] = 'pause'
    pause['cp'] = length
    pause['sq'] = sq
    hb_list.append(pause)

    # videoend
    ve = template.copy()
    sq += 1
    ve['et'] = 'videoend'
    ve['cp'] = length
    ve['sq'] = sq
    hb_list.append(ve)

    requests.post(heartbeat_url, headers=headers2, data=json.dumps({'heart_data': hb_list}))

    print('+ 是否完成观看：', get_rate(id, headers2))